

import SSF.OS.*;
import SSF.Net.*;
import SSF.Net.Util.*;
import com.renesys.raceway.DML.*;

/** A simple traceroute protocol. To see a trace from a given host
 *  to a given target, configure as shown in <TT>config()</TT>, and
 *  make sure that ICMP is installed in all routers along the route
 *  and in the target host/router. <P>
 *  Only one instance of traceroute per host may be installed in this
 *  version.
 */
public class traceroute extends ProtocolSession {

    //----------------------------------------------------------------------
    // DEFAULTS

    public static final int PROBE_COUNT = 3;   // three packets back2back
    public static final double PROBE_SPREAD = 10e-6;  // microseconds apart
    public static final double TIMEOUT = 3.;   // three seconds, then give up 
    public static final double WAIT_TIME = 1.; // trace after 1 second
    public static final int PROBE_BYTES = 32;  // echo payload bytes
    public static final double WATCHDOG_INTERVAL = 0.1;  // between polls

    boolean show_nhi = false;  // print nhi address in traceroute output
    boolean DEBUG = false;     // print verbose diagnostics

    /** Configure the traceroute protocol. The supported attributes are
     *  as in this example:
     *  <PRE>
     *  ProtocolSession [ name troute use traceroute
     *    dest 5:3(0)       # traceroute target NHI address
     *    after 25.0        # delay after simulation start
     *    probes 3          # number of consecutive back2back probe packets,
     *                      # if omitted, default is 3
     *    bytes  32         # number of echo payload bytes,
     *                      # if omitted, default is 32
     *    maxhops 128       # max number of hops, if omitted the default
     *                      # is SSF.OS.IpHeader.DEFAULT_TIME_TO_LIVE
     *    timeout  3.0      # time window in which an ICMP message
     *                      # is expected, if none is received skip to next hop.
     *                      # If omitted, default timeout is 3.0 seconds.
     *    show_nhi true     # print nhi addresses along the route,
     *                      # if omitted or false, only IP addresses shown.
     *  ]
     *  </PRE>
     */
    public void config(Configuration cfg) throws configException {
	super.config(cfg);

	destnhi =   (String)cfg.findSingle("dest");
	localhost = (Host)inGraph();
	destip = SSF.Net.Util.IP_s.StrToInt(localhost.getNet().nhi_to_ip(destnhi));
	
	String len =    (String)cfg.findSingle("bytes");
	String aft =    (String)cfg.findSingle("after");
	String max =    (String)cfg.findSingle("maxhops");
	String probes = (String)cfg.findSingle("probes");
	String tout   = (String)cfg.findSingle("timeout");
	String str   = (String)cfg.findSingle("show_nhi");
	if (str != null)
          show_nhi = Boolean.valueOf(str).booleanValue();

	bytes = (null==len?PROBE_BYTES:Integer.parseInt(len));
	after = (null==aft?WAIT_TIME:Double.parseDouble(aft));
	maxhops = (null==max?IpHeader.DEFAULT_TIME_TO_LIVE:
		   Integer.parseInt(max));
	timeout = (null==tout?TIMEOUT:Double.parseDouble(tout));

	pcount = (null==probes?PROBE_COUNT:Integer.parseInt(probes));
    }

    Host localhost;
    String destnhi;
    long start,elapsed;
    double after,timeout;
    int waiting,ttl,pid;
    boolean finished;
    long times[];
    int destip,bytes,maxhops,pcount;

    ICMP icmp;

    /* Send out groups of (default) three ECHO REQUEST packets 1us apart
     * with steadily increasing ttls.  Get ICMP replies via push()
     * to assemble the timing picture.   The timer set here forces
     * us to move on to the next hop if an intermediate hop has
     * failed to reply within the max window of <TT>timeout</TT> seconds.
     */
    public void trace() {

	if(show_nhi)
	  System.out.println("Traceroute from "+localhost.nhi+" to "+
	                      destnhi+" "+IP_s.IPtoString(destip)+" ("+
			      maxhops+" hops max, "+bytes+" byte packets)");
	else
	  System.out.println("Traceroute to "+IP_s.IPtoString(destip)+" ("+
			      maxhops+" hops max, "+bytes+" byte packets)");
	// ttl = 1;
	ttl = 0;  //  cf.change in SSF.OS.IP
	finished = false;
	times = new long[pcount];
	pingSet();
	(new Timer(inGraph(),Net.seconds(WATCHDOG_INTERVAL)) {
		public void callback() {
		    elapsed = inGraph().now()-start;
		    if (elapsed>Net.seconds(timeout)) nextHop(-1,true);
		    if (!finished) set();
		}
	    }).set();
    }

    public void init() {
      try {
        icmp = (ICMP)inGraph().SessionForName("icmp");

        // register with ICMP to receive all ICMP packets
        // destined to this host:
        icmp.listen(this);
	
        // begin traceroute at time "after"
        (new Timer(inGraph(),Net.seconds(after)) {
	  public void callback() {
            trace();
          }
        }).set();
      } catch (ProtocolException pex) {
        pex.printStackTrace();
      }
    }

    /** Receive ICMP messages from IP.
     */
    public boolean push(ProtocolMessage msg, ProtocolSession from) {
	if (msg instanceof ICMPHeader) {
	
	    ICMPHeader ih = (ICMPHeader)msg;
	    if(DEBUG)
	      System.out.println("traceroute.push(): received "+
	                          ih.toString());
	    switch(ih.icmpType) {
	    case ICMP.ICMP_TIME_EXCEEDED:
		// small appx.:sent at start (+{0,1,2us})
		if(DEBUG)
		  System.out.println("waiting= "+waiting+" rtt(0)= "+rtt(0));
		// here get an error if this instance of traceroute is not
		// the one that expects ICMP_TIME_EXCEEDED!
		// Only one instance of traceroute allowed per host
		// in this version.
		times[--waiting]=rtt(0);
		if (0==waiting) nextHop(ih.ipHeader().SOURCE_IP,true);
		break;

	    case ICMP.ICMP_ECHO_RESPONSE:
		if (pid==ih.id) {
		    // was sent at start plus seq*1us
		    times[--waiting]=rtt(ih.seq);
		    if (0==waiting) nextHop(ih.ipHeader().SOURCE_IP,false);
		}
		break;
		
	    default:
		// We may get unreachable etc. if routing is in flux ..
		//System.out.println("** traceroute received unknown "+ih);
		break;
	    }
	}
	return true;
    }

    long rtt(int seq) {
	return (inGraph().now()-(start+Net.seconds(seq*PROBE_SPREAD)));
    }

    public void nextHop(int srcip, boolean more) {
      if(show_nhi)
        //System.out.print((ttl-1)+"  "+
        System.out.print(ttl+"  "+
			 (srcip==-1?"!H":
			  localhost.getNet().ip_to_nhi(IP_s.IPtoString(srcip))
			  +"  "+IP_s.IPtoString(srcip)+"  "));
      else
	//System.out.print((ttl-1)+"  "+
	System.out.print(ttl+"  "+
			 (srcip==-1?"!H":IP_s.IPtoString(srcip)+"  "));
      for (int n=0; n<times.length; n++)
        System.out.print(times[n]==0?"* ":
                         ((int)(times[n]*1000./Net.frequency)+" ms "));
      System.out.println();
      if (false == (finished = (!more || ttl>maxhops)))
        pingSet();
      else
        icmp.finished(this);
    }

    void pingSet() {
	start = inGraph().now();
	for (int n=0; n<times.length; n++) times[n]=0;
	try {
	    pid = icmp.ping(bytes,destip,waiting=times.length,
			    PROBE_SPREAD,++ttl);
	} catch (ProtocolException pex) {
	    pex.printStackTrace();
	}
    }
}
